package simulation;

import java.awt.Color;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.HashMap;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.border.EtchedBorder;
import javax.swing.border.LineBorder;

/**
 * Authors: Ben Lee
 * Purpose: Boundary object.  Allows the user to modify the schedule of Simulation.
 * The user can drag and drop labels, which are automatically mapped to planes and timestamps.
 * Last modified: 11/21/11
 * Tested by:
 * Date tested:
 */

public class ChangeScheduleDialog extends JFrame implements MouseListener,MouseMotionListener{
	private Simulation sim; //Pointer to sim, used to modify schedule.
	private HashMap<JLabel,Plane> planeMap; //Map of JLabels to Planes, generated from schedule in constructor.
	private JLabel heldLabel; //Label being dragged and dropped.
	private JLabel timeCursor; //Empty label used to show the current time;
	
	final int MAXTIME = 250; //Maximum number of time ticks in a simulation;
	final int WIDTH = 600;
	final int HEIGHT = 300;
	final int LABEL_HEIGHT = 20;
	final int LABEL_WIDTH = 70;
	final int LABEL_SPACING = 2; //Gap between two labels;
	
	/**
	 * Author: Ben Lee, Ross Hadden
	 * Purpose: Initializes variables and generates the schedule display.  Sets up action listeners.
	 * Last modified: 11/21/11
	 * Tested by:
	 * Date tested:
	 */	
	public MouseEvent mouse;
	
	public ChangeScheduleDialog(Simulation s){
		sim = s;
		planeMap = new HashMap<JLabel,Plane>();
		
		this.setLayout(null);
		//Construct a label for each 
		for(Event e: sim.eventPriorityQueue){
			if(e instanceof Plane){
				Plane p = (Plane) e;
				//TODO (Low priority): Think of a better way to display the plane than a time and a JLabel.
				JLabel label = new JLabel(getPlaneText(p,Integer.toString(p.getEventTime())));
				label.setIcon(new ImageIcon("img/planeIcon.jpg"));
				
				planeMap.put(label, p);
				
				label.setBorder(new EtchedBorder());
				//Start the label at the top of the window.  Walk it down until it doesn't overlap any other labels,
				//Or until it hits the bottom of the window.
				label.setBounds(getLocation(p.getEventTime()),LABEL_SPACING,LABEL_WIDTH,LABEL_HEIGHT);
				while(intersection(label) && label.getLocation().y < HEIGHT - LABEL_HEIGHT){
					label.setLocation(label.getLocation().x, label.getLocation().y + LABEL_HEIGHT + LABEL_SPACING);
				}

				label.addMouseListener(this);
				label.addMouseMotionListener(this);
				this.add(label);
			}
		}
		
		timeCursor = new JLabel();
		this.add(timeCursor);
		timeCursor.setBounds(0,0,1,HEIGHT);
		timeCursor.setBorder(new LineBorder(Color.BLACK));
		
		this.setSize(WIDTH,HEIGHT+45);
		this.setLocation(0, 600);
		this.setDefaultCloseOperation(HIDE_ON_CLOSE); //Can be unhidden by SimulationDisplay.
		this.setVisible(true);
	}
	//These methods have to be implemented to use MouseListener.
	public void mouseClicked(MouseEvent e){}
	public void mouseEntered(MouseEvent e){}
	public void mouseExited(MouseEvent e){}
	
	/**
	 * Author: Ben Lee, Ross Hadden
	 * Purpose: Used to start a drag-and-drop action.
	 * Last modified: 11/22/11
	 * Tested by:
	 * Date tested:
	 */
	public void mousePressed(MouseEvent e){
		//This is called for all mouse events, so make sure it's a JLabel.
		mouse = e;
		if(e.getComponent() instanceof JLabel){
			heldLabel = (JLabel) e.getComponent();
//			System.out.println("Clicked on label " + heldLabel.getBounds() + "->" + planeMap.get(heldLabel));
			heldLabel.setForeground(Color.RED);
		}
	}
	/**
	 * Author: Ben Lee, Ross Hadden
	 * Purpose: Used to end a drag-and-drop action.  Updates the plane's scheduled time.  Makes sure the
	 * chosen time is valid.
	 * Last modified: 11/22/11
	 * Tested by:
	 * Date tested:
	 */
	public void mouseReleased(MouseEvent e){
		//Position relative to the frame, not the label.
		//getMousePosition(true) means get the mouse position even if the mouse is on top of a different component.
		Point pos = this.getContentPane().getMousePosition(true);
		
		//	Makes the plane boxes drop exactly where the user's mouse is,
		//	rather than to the top-left of the box.
		//Check if the mouse is outside the window.
		if(pos == null){
			pos = heldLabel.getLocation();
		}
		else
			pos.setLocation(
				pos.getX() - e.getX(),
				pos.getY() - e.getY()
			);
		
		Plane plane = planeMap.get(heldLabel);
		
		/** Don't allow a plane to be scheduled earlier than the current time.  Snap it to the current time instead.
		 * Setting the time to sim.timeCounter doesn't work (probably because the events have already been processed
		 * for that time.  So set it to sim.timeCounter + 1.
		 */
		if(getTime(pos.x) < sim.timeCounter + 1)
			plane.setEventTime(sim.timeCounter + 1);
		else
			plane.setEventTime(getTime(pos.x));
		sim.eventPriorityQueue.remove(plane);
		sim.eventPriorityQueue.offer(plane);
		
		int realTime = plane.getEventTime(); //Get what the time was actually set to.
		
		heldLabel.setLocation(getLocation(realTime),pos.y);
		
		heldLabel.setForeground(Color.BLACK);
		heldLabel.setText(getPlaneText(plane,Integer.toString(realTime)));
		
		heldLabel = null;
	}
	/**
	 * Author: Ben Lee, Ross Hadden
	 * Purpose: Updates a plane's position while it is being dragged.
	 * Last modified: 11/22/11
	 * Tested by:
	 * Date tested:
	 */
	public void mouseDragged(MouseEvent e){
		Point pos = this.getContentPane().getMousePosition(true);
		//	Makes the plane boxes drop exactly where the user's mouse is,
		//	rather than to the top-left of the box.
		if(pos != null){
			pos.setLocation(
					pos.getX() - mouse.getX(),
					pos.getY() - mouse.getY()
				);
			heldLabel.setLocation(pos);
			heldLabel.setText(getPlaneText(planeMap.get(heldLabel),Integer.toString(getTime(pos.x))));
		}
	}
	//Needed to implement MouseMotionListener.
	public void mouseMoved(MouseEvent e){}
	
	//Converts a timeline position to a time.
	public int getTime(int xPos){ 
		return MAXTIME * xPos / WIDTH;
	}
	
	//Converts a time to a timeline position
	public int getLocation(int time){ 
		return WIDTH * time / MAXTIME;
	}
	
	/**
	 * Author: Ben Lee
	 * Purpose: Removes a plane from the display.  Called by Simulation whenever a plane is removed from the schedule.
	 * Last modified: 11/15/11
	 * Tested by:
	 * Date tested:
	 */
	public void removePlane(Plane toRemove){
		//Have to move the labels to a separate array to avoid a ConcurrentModificationException
		JLabel[] labels = new JLabel[planeMap.size()];
		planeMap.keySet().toArray(labels);
		for(JLabel l : labels){
			if(planeMap.get(l) == toRemove){
				planeMap.remove(l);
				l.setVisible(false);
			}
		}
	}
	
	/**
	 * Author: Ben Lee
	 * Purpose: Returns true if the given label overlaps any other labels on the schedule.  Used in the constructor to
	 * neatly lay out the schedule.
	 * Last modified: 11/21/11
	 * Tested by:
	 * Date tested:
	 */
	public boolean intersection(JLabel l){
		for(JLabel other : planeMap.keySet()){
//			System.out.println("Comparing " + l.getBounds() + " and " + other.getBounds());
			//Don't compare a rectangle to itself.
			if(!l.equals(other) && l.getBounds().intersects(other.getBounds()))
				return true;
		}
		return false;
	}
	private String getPlaneText(Plane p,String time){
		if(p.getType() == simulation.Type.LANDING){
			return "L: " + time;
		} else {
			return "T: " + time;
		}
	}
	public void updateTime(int time){
		timeCursor.setLocation(getLocation(time), 0);
	}
}
